package com.liuhengjia.aiknowledgebase.third;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.lark.oapi.Client;
import com.lark.oapi.core.request.RequestOptions;
import com.lark.oapi.core.response.BaseResponse;
import com.lark.oapi.service.docx.v1.model.RawContentDocumentReq;
import com.lark.oapi.service.docx.v1.model.RawContentDocumentResp;
import com.lark.oapi.service.docx.v1.model.RawContentDocumentRespBody;
import com.lark.oapi.service.drive.v1.model.File;
import com.lark.oapi.service.drive.v1.model.ListFileReq;
import com.lark.oapi.service.drive.v1.model.ListFileResp;
import com.lark.oapi.service.drive.v1.model.ListFileRespBody;
import com.liuhengjia.aiknowledgebase.config.FeiShuConfig;
import com.liuhengjia.aiknowledgebase.third.model.FeiShuAkRequest;
import com.liuhengjia.aiknowledgebase.third.model.FeiShuAkResponse;
import com.liuhengjia.aiknowledgebase.util.Jsons;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * 飞书服务层
 */
@Service
@RequiredArgsConstructor
public class FeiShuService {
    // <a href="https://open.feishu.cn/document/server-docs/authentication-management/access-token/app_access_token_internal">...</a>
    public static final String AK_URL = "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal";
    private static final Cache<String, String> ACCESS_TOKEN_CACHE = Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(100, TimeUnit.MINUTES)
            .build();
    private final Client client;
    private final RestTemplate restTemplate;
    private final FeiShuConfig feishuConfig;

    /**
     * 获取访问 Token
     *
     * @param appId 应用 ID
     * @return String
     */
    private String getAccessToken(String appId) {
        return ACCESS_TOKEN_CACHE.get(appId, s -> {
            HttpEntity<FeiShuAkRequest> request = new HttpEntity<>(new FeiShuAkRequest(feishuConfig.getAppId(),
                    feishuConfig.getAppSecret()));
            var response = Optional.of(restTemplate.exchange(AK_URL, HttpMethod.POST, request, FeiShuAkResponse.class));
            if (response.map(HttpEntity::getBody).map(FeiShuAkResponse::getCode).filter(code -> code.equals(0)).isEmpty()) {
                throw new RuntimeException("获取 Access Token 失败，则响应体为 %s".formatted(response.get()));
            }
            return response.get().getBody().getAppAccessToken();
        });
    }

    /**
     * 读取单个文档
     *
     * @param docId 文档 ID
     * @return String
     */
    public String readDoc(String docId) {
        RawContentDocumentReq req = RawContentDocumentReq.newBuilder()
                .lang(0)
                .documentId(docId)
                .build();
        Optional<RawContentDocumentResp> resp;
        try {
            resp = resolveException(client.docx().v1().document()
                    .rawContent(req, RequestOptions.newBuilder()
                            .userAccessToken(getAccessToken(feishuConfig.getAppId()))
                            .build()));
        } catch (Exception e) {
            throw new RuntimeException("failed to read the document", e);
        }
        return resp.map(BaseResponse::getData)
                .map(RawContentDocumentRespBody::getContent)
                .orElse("");
    }

    /**
     * 遍历目录下的文件
     * TODO: 1、多级目录；2、多次读取
     *
     * @return List<File>
     */
    public List<File> listDocs() {
        //If the pagination size is exceeded, use hasMore to fetch the next batch, the logic for fetching the next batch is not written here
        ListFileReq req = ListFileReq.newBuilder()
                .pageSize(50)
                .folderToken(feishuConfig.getRootFolder())
                .build();
        Optional<ListFileResp> resp;
        try {
            resp = resolveException(client.drive().v1().file().list(req, RequestOptions.newBuilder()
                    .userAccessToken(getAccessToken(feishuConfig.getAppId()))
                    .build()));
        } catch (Exception e) {
            throw new RuntimeException("无法列出文档", e);
        }
        return resp.map(BaseResponse::getData)
                .map(ListFileRespBody::getFiles)
                .map(Arrays::asList)
                .orElse(new ArrayList<>());
    }

    /**
     * 解决异常
     *
     * @param response 响应
     * @param <T>      BaseResponse
     * @return <T> Optional<T>
     */
    private <T extends BaseResponse<?>> Optional<T> resolveException(T response) {
        if (!response.success()) {
            throw new RuntimeException("调用 API 函数失败，响应体为 %s".formatted(Jsons.object2Json(response)));
        }
        return Optional.of(response);
    }
}
